package net.aiion.weave.spi.bytecode;

import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.ServiceLoader;

import net.aiion.weave.spi.bytecode.events.BytecodeEvent;
import net.aiion.weave.spi.resources.Resource;

public abstract class Bytecode {
    private static Bytecode instance;

    private static BytecodeEventFactory eventFactory;
    private static BytecodeSourceFactory sourceFactory;
    private static BytecodeSinkFactory sinkFactory;
    private static BytecodeStore store;

    protected Bytecode() {
    }

    public static BytecodeEventFactory getEventFactory() {
        if (eventFactory == null) {
            eventFactory = getInstance().createEventFactory();
        }

        return eventFactory;
    }

    public static BytecodeSourceFactory getSourceFactory() {
        if (sourceFactory == null) {
            sourceFactory = getInstance().createSourceFactory();
        }

        return sourceFactory;
    }

    public static BytecodeSinkFactory getSinkFactory() {
        if (sinkFactory == null) {
            sinkFactory = getInstance().createSinkFactory();
        }

        return sinkFactory;
    }

    public static BytecodeStore getStore() {
        if (store == null) {
            store = getInstance().createStore();
        }

        return store;
    }

    public static BytecodeSource createReader(final String aClassName) {
        return getSourceFactory().createReader(aClassName);
    }

    public static BytecodeSource createReader(final Resource aResource) {
        return getSourceFactory().createReader(aResource);
    }

    public static BytecodeFinder createFinder(final String aClassName) {
        return getSourceFactory().createFinder(aClassName);
    }

    public static BytecodeFinder createFinder(final Resource aResource) {
        return getSourceFactory().createFinder(aResource);
    }

    public static BytecodeGenerator createGenerator() {
        return getSourceFactory().createGenerator();
    }
    
    public static BytecodeGenerator createGenerator(final BytecodeSink aBytecodeSink) {
        final BytecodeGenerator bytecodeGenerator = createGenerator();
        bytecodeGenerator.addSink(aBytecodeSink);
        return bytecodeGenerator;
    }
    
    public static BytecodeGenerator createGenerator(final Collection<BytecodeEvent> aTarget) {
        return createGenerator(createWriter(aTarget));
    }

    public static BytecodeSink createWriter(final OutputStream aOutputStream) {
        return getSinkFactory().createWriter(aOutputStream);
    }

    public static BytecodeSink createWriter(final Collection<BytecodeEvent> aTarget) {
        return getSinkFactory().createWriter(aTarget);
    }

    public static BytecodeSink createWriter() {
        return getSinkFactory().createWriter();
    }

    public static BytecodeType findTypeByName(final String aName) {
        return getStore().findTypeByName(aName);
    }

    public static Class<?> findClassByName(final String aName) {
        return getStore().findClassByName(aName);
    }

    public static Collection<BytecodeType> findTypesByInterfaceName(final String aInterfaceName) {
        return getStore().findTypesByInterfaceName(aInterfaceName);
    }

    public static Collection<BytecodeType> findTypesByInterfaceClass(final Class<?> aInterfaceClass) {
        return getStore().findTypesByInterfaceClass(aInterfaceClass);
    }

    public static Collection<Class<?>> findClassesByInterfaceName(final String aInterfaceName) {
        return getStore().findClassesByInterfaceName(aInterfaceName);
    }

    public static <T> Collection<Class<? extends T>> findClassesByInterfaceClass(final Class<T> aInterfaceClass) {
        return getStore().findClassesByInterfaceClass(aInterfaceClass);
    }

    public static Collection<BytecodeType> findTypesByAnnotationName(final String aAnnotationName) {
        return getStore().findTypesByAnnotationName(aAnnotationName);
    }

    public static Collection<BytecodeType> findTypesByAnnotationClass(final Class<? extends Annotation> aAnnotationClass) {
        return getStore().findTypesByAnnotationClass(aAnnotationClass);
    }

    public static Collection<Class<?>> findClassesByAnnotationName(final String aAnnotationName) {
        return getStore().findClassesByAnnotationName(aAnnotationName);
    }

    public static Collection<Class<?>> findClassesByAnnotationClass(final Class<? extends Annotation> aAnnotationClass) {
        return getStore().findClassesByAnnotationClass(aAnnotationClass);
    }

    protected static Bytecode getInstance() {
        if (instance == null) {
            for (Bytecode bytecode : BytecodeServiceLoader.SERVICE_LOADER) {
                instance = bytecode;
                break;
            }
        }

        return instance;
    }

    protected abstract BytecodeEventFactory createEventFactory();

    protected abstract BytecodeSourceFactory createSourceFactory();

    protected abstract BytecodeSinkFactory createSinkFactory();

    protected abstract BytecodeStore createStore();

    private static final class BytecodeServiceLoader {
        private static final ServiceLoader<Bytecode> SERVICE_LOADER = ServiceLoader.load(Bytecode.class);

        private BytecodeServiceLoader() {
        }
    }
}
